// GameContext.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Threading;

using Supremacy.Annotations;
using Supremacy.Diplomacy;
using Supremacy.Entities;
using Supremacy.IO.Serialization;
using Supremacy.Personnel;
using Supremacy.Resources;
using Supremacy.Scripting;
using Supremacy.Scripting.Events;
using Supremacy.Tech;
using Supremacy.Text;
using Supremacy.Types;
using Supremacy.Universe;
using Supremacy.Collections;
using Supremacy.AI;

namespace Supremacy.Game
{
    public interface IGameContext : IOwnedDataSerializableAndRecreatable
    {
        bool IsMultiplayerGame { get; }

        void LoadStrings([NotNull] ITextDatabase textDatabase);

        ICollection<ScriptedEvent> ScriptedEvents { get; }

        /// <summary>
        /// Gets the game data tables.
        /// </summary>
        /// <value>The tables.</value>
        GameTables Tables { get; }

        /// <summary>
        /// Gets the game options with which this instance was created.
        /// </summary>
        /// <value>The options.</value>
        GameOptions Options { get; }

        /// <summary>
        /// Gets or sets the current game mod.
        /// </summary>
        /// <value>The current game mod, or <c>null</c> if no mod is loaded.</value>
        GameMod GameMod { get; }

        /// <summary>
        /// Gets the civilizations in the current game.
        /// </summary>
        /// <value>The civilizations.</value>
        CivDatabase Civilizations { get; }

        /// <summary>
        /// Gets the civilization managers for the civilizations in the current game.
        /// </summary>
        /// <value>The civilization managers.</value>
        /// <remarks>
        /// In the server-side instance, the returned collection contains managers
        /// for every civilization in the current game.  The client-side instance
        /// contains only the manager for the local player's civilization.
        /// </remarks>
        CivilizationManagerMap CivilizationManagers { get; }

        /// <summary>
        /// Gets the races in the current game.
        /// </summary>
        /// <value>The races.</value>
        RaceDatabase Races { get; }

        /// <summary>
        /// Gets the universe manager for the current game.
        /// </summary>
        /// <value>The universe manager.</value>
        UniverseManager Universe { get; }

        /// <summary>
        /// Gets the tech database for the current game.
        /// </summary>
        /// <value>The tech database.</value>
        TechDatabase TechDatabase { get; }

        /// <summary>
        /// Gets or sets the turn number for the current game.
        /// </summary>
        /// <value>The turn number.</value>
        TurnNumber TurnNumber { get; set; }

        /// <summary>
        /// Gets the research matrix for the current game.
        /// </summary>
        /// <value>The research matrix.</value>
        ResearchMatrix ResearchMatrix { get; }

        /// <summary>
        /// Gets the map of sector claims for the current game.
        /// </summary>
        /// <value>The map of sector claims.</value>
        SectorClaimGrid SectorClaims { get; }

        TechTreeMap TechTrees { get; }
        StrategyDatabase StrategyDatabase { get; }

        /// <summary>
        /// Gets the map of relationships between all civilizations in the current game.
        /// </summary>
        /// <value>The map of relationships.</value>
        RelationshipMap Relationships { get; }

        /// <summary>
        /// Gets a double-keyed map of the diplomacy data for every pair of civilizations for the current game.
        /// </summary>
        /// <value>The diplomacy data map.</value>
        CivilizationPairedMap<IDiplomacyDataExtended> DiplomacyData { get; }

        /// <summary>
        /// Gets a double-keyed map of the diplomacy states for every pair of civilizations for the current game.
        /// </summary>
        /// <value>The diplomacy status map.</value>
        CivilizationPairedMap<DiplomacyStatus> DiplomacyStatus { get; }

        /// <summary>
        /// Gets the <see cref="Civilization"/>-to-<see cref="Diplomat"/> map for the current game.
        /// </summary>
        /// <value>The <see cref="Civilization"/>-to-<see cref="Diplomat"/> map.</value>
        CivilizationKeyedMap<Diplomat> Diplomats { get; }

        /// <summary>
        /// Generates a new object ID for use in the current game.
        /// </summary>
        /// <returns>The object ID.</returns>
        GameObjectID GenerateID();

        /// <summary>
        /// Gets the agent database for the current game.
        /// </summary>
        /// <value>The agent database.</value>
        AgentDatabase AgentDatabase { get; }
    }

    /// <summary>
    /// Holds all data pertaining to a specific game instance.  The class also
    /// functions as a static stack of class instances, with the <see cref="Current"/>
    /// property pointing to the instance at the top of the stack.
    /// </summary>
    [Serializable]
    public sealed class GameContext : IGameContext
    {
        #region Instance Members
        #region Fields
        private int _nextObjectId;
        private TurnNumber _turnNumber = 0;
        private GameOptions _options;
        private GameMod _gameMod;
        private CivDatabase _civilizations;
        private CivilizationManagerMap _civManagers;
        private RaceDatabase _races;
        private UniverseManager _universe;
        private TechDatabase _techDatabase;
        [NonSerialized]
        private GameTables _tables;
        private ResearchMatrix _researchMatrix;
        private SectorClaimGrid _sectorClaims;
        private RelationshipMap _relationships;
        private CivilizationPairedMap<DiplomacyStatus> _diplomacyStatus;
        private TechTreeMap _techTrees;
        private CivilizationPairedMap<IDiplomacyDataExtended> _diplomacyData;
        private AgreementMatrix _agreementMatrix;
        private CivilizationKeyedMap<Diplomat> _diplomats;
        private StrategyDatabase _strategyDatabase;
        private ICollection<ScriptedEvent> _scriptedEvents;
        private AgentDatabase _agentDatabase;
        #endregion

        public void SerializeOwnedData(SerializationWriter writer, object context)
        {
            writer.Write(this.IsMultiplayerGame);
            writer.WriteOptimized(_nextObjectId);
            writer.WriteOptimized((ushort)_turnNumber);
            writer.WriteObject(_options);
            writer.WriteObject(_gameMod);
            writer.WriteObject(_civilizations);
            writer.WriteObject(_civManagers);
            writer.WriteObject(_races);
            writer.WriteObject(_universe);
            writer.WriteObject(_techDatabase);
            writer.WriteObject(_researchMatrix);
            writer.WriteObject(_sectorClaims);
            writer.WriteObject(_relationships);
            writer.WriteObject(_techTrees);
            writer.WriteObject(_diplomacyStatus);
            writer.WriteObject(_diplomacyData);
            writer.WriteObject(_agreementMatrix);
            writer.WriteObject(_diplomats);
            writer.WriteObject(_strategyDatabase);
            writer.WriteObject(_scriptedEvents);
            writer.WriteObject(_agentDatabase);
        }

        public void DeserializeOwnedData(SerializationReader reader, object context)
        {
            PushThreadContext(this);

            try
            {
                this.IsMultiplayerGame = reader.ReadBoolean();
                _nextObjectId = reader.ReadOptimizedInt32();
                _turnNumber = reader.ReadOptimizedUInt16();
                _options = reader.Read<GameOptions>();
                _gameMod = reader.Read<GameMod>();
                _civilizations = reader.Read<CivDatabase>();
                _civManagers = reader.Read<CivilizationManagerMap>();
                _races = reader.Read<RaceDatabase>();
                _universe = reader.Read<UniverseManager>();
                _techDatabase = reader.Read<TechDatabase>();
                _researchMatrix = reader.Read<ResearchMatrix>();
                _sectorClaims = reader.Read<SectorClaimGrid>();
                _relationships = reader.Read<RelationshipMap>();
                _techTrees = reader.Read<TechTreeMap>();
                _diplomacyStatus = reader.Read<CivilizationPairedMap<DiplomacyStatus>>();
                _diplomacyData = reader.Read<CivilizationPairedMap<IDiplomacyDataExtended>>();
                _agreementMatrix = reader.Read<AgreementMatrix>();
                _diplomats = reader.Read<CivilizationKeyedMap<Diplomat>>();
                _strategyDatabase = reader.Read<StrategyDatabase>();
                _scriptedEvents = reader.Read<ICollection<ScriptedEvent>>();
                _agentDatabase = reader.Read<AgentDatabase>();
            }
            finally
            {
                PopThreadContext();
            }
        }

        public bool IsMultiplayerGame { get; internal set; }

        public void LoadStrings([NotNull] ITextDatabase textDatabase)
        {
            if (textDatabase == null)
                throw new ArgumentNullException("textDatabase");

            var techObjectTable = textDatabase.GetTable<ITechObjectTextDatabaseEntry>();

            foreach (var design in _techDatabase)
            {
                ITextDatabaseEntry<ITechObjectTextDatabaseEntry> entry;
                if (!techObjectTable.TryGetEntry(design.Key, out entry))
                    continue;
                design.TextDatabaseEntry = entry.GetLocalizedEntry(ResourceManager.CurrentLocale);
            }
        }

        #region Properties
        /// <summary>
        /// Gets the game data tables.
        /// </summary>
        /// <value>The tables.</value>
        public GameTables Tables
        {
            get { return _tables; }
            internal set { _tables = value; }
        }

        /// <summary>
        /// Gets the game options with which this instance was created.
        /// </summary>
        /// <value>The options.</value>
        public GameOptions Options
        {
            get { return _options; }
            internal set { _options = value; }
        }

        /// <summary>
        /// Gets or sets the current game mod.
        /// </summary>
        /// <value>The current game mod, or <c>null</c> if no mod is loaded.</value>
        public GameMod GameMod
        {
            get { return _gameMod; }
            set { _gameMod = value; }
        }

        /// <summary>
        /// Gets the civilizations in the current game.
        /// </summary>
        /// <value>The civilizations.</value>
        public CivDatabase Civilizations
        {
            get { return _civilizations; }
            internal set { _civilizations = value; }
        }

        /// <summary>
        /// Gets the civilization managers for the civilizations in the current game.
        /// </summary>
        /// <value>The civilization managers.</value>
        /// <remarks>
        /// In the server-side instance, the returned collection contains managers
        /// for every civilization in the current game.  The client-side instance
        /// contains only the manager for the local player's civilization.
        /// </remarks>
        public CivilizationManagerMap CivilizationManagers
        {
            get { return _civManagers; }
            internal set { _civManagers = value; }
        }

        /// <summary>
        /// Gets the races in the current game.
        /// </summary>
        /// <value>The races.</value>
        public RaceDatabase Races
        {
            get { return _races; }
            internal set { _races = value; }
        }

        /// <summary>
        /// Gets the universe manager for the current game.
        /// </summary>
        /// <value>The universe manager.</value>
        public UniverseManager Universe
        {
            get { return _universe; }
            internal set { _universe = value; }
        }

        /// <summary>
        /// Gets the tech database for the current game.
        /// </summary>
        /// <value>The tech database.</value>
        public TechDatabase TechDatabase
        {
            get { return _techDatabase; }
            internal set { _techDatabase = value; }
        }

        public ICollection<ScriptedEvent> ScriptedEvents
        {
            get { return _scriptedEvents; }
            internal set { _scriptedEvents = value; }
        }

        public AgentDatabase AgentDatabase
        {
            get { return _agentDatabase; }
            internal set { _agentDatabase = value; }
        }

        public event EventHandler TurnNumberChanged;

        private void OnTurnNumberChanged()
        {
            var handler = this.TurnNumberChanged;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }

        /// <summary>
        /// Gets or sets the turn number for the current game.
        /// </summary>
        /// <value>The turn number.</value>
        public TurnNumber TurnNumber
        {
            get { return _turnNumber; }
            set
            {
                if (Equals(_turnNumber, value))
                    return;
                _turnNumber = value;
                OnTurnNumberChanged();
            }
        }

        /// <summary>
        /// Gets the research matrix for the current game.
        /// </summary>
        /// <value>The research matrix.</value>
        public ResearchMatrix ResearchMatrix
        {
            get { return _researchMatrix; }
            internal set { _researchMatrix = value; }
        }

        /// <summary>
        /// Gets the map of sector claims for the current game.
        /// </summary>
        /// <value>The map of sector claims.</value>
        public SectorClaimGrid SectorClaims
        {
            get { return _sectorClaims; }
            internal set { _sectorClaims = value; }
        }

        public TechTreeMap TechTrees
        {
            get { return _techTrees; }
            internal set { _techTrees = value; }
        }

        public StrategyDatabase StrategyDatabase
        {
            get { return _strategyDatabase; }
            internal set { _strategyDatabase = value; }
        }

        /// <summary>
        /// Gets the map of relationships between all civilizations in the current game.
        /// </summary>
        /// <value>The map of relationships.</value>
        public RelationshipMap Relationships
        {
            get { return _relationships; }
            internal set { _relationships = value; }
        }

        /// <summary>
        /// Gets a double-keyed map of the diplomacy data for every pair of civilizations for the current game.
        /// </summary>
        /// <value>The diplomacy data map.</value>
        public CivilizationPairedMap<IDiplomacyDataExtended> DiplomacyData
        {
            get { return _diplomacyData; }
            internal set { _diplomacyData = value; }
        }

        /// <summary>
        /// Gets a double-keyed map of the diplomacy states for every pair of civilizations for the current game.
        /// </summary>
        /// <value>The diplomacy status map.</value>
        public CivilizationPairedMap<DiplomacyStatus> DiplomacyStatus
        {
            get { return _diplomacyStatus; }
            internal set { _diplomacyStatus = value; }
        }

        /// <summary>
        /// Gets the <see cref="Civilization"/>-to-<see cref="Diplomat"/> map for the current game.
        /// </summary>
        /// <value>The <see cref="Civilization"/>-to-<see cref="Diplomat"/> map.</value>
        public CivilizationKeyedMap<Diplomat> Diplomats
        {
            get { return _diplomats; }
            internal set { _diplomats = value; }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="GameContext"/> class.
        /// </summary>
        public GameContext() {}
        #endregion

        #region Methods
        /// <summary>
        /// Generates a new object ID for use in the current game.
        /// </summary>
        /// <returns>The object ID.</returns>
        public GameObjectID GenerateID()
        {
            return _nextObjectId++;
        }
        #endregion
        #endregion

        #region Static Members
        private static readonly ConcurrentStack<GameContext> _stack = new ConcurrentStack<GameContext>();
        private static readonly Mutex _stackLock = new Mutex();

        [ThreadStatic]
        private static System.Collections.Concurrent.ConcurrentStack<GameContext> _threadStack = new System.Collections.Concurrent.ConcurrentStack<GameContext>();

        private static System.Collections.Concurrent.ConcurrentStack<GameContext> ThreadStack
        {
            get
            {
                if (_threadStack == null)
                    _threadStack = new System.Collections.Concurrent.ConcurrentStack<GameContext>();
                return _threadStack;
            }
        }

        /// <summary>
        /// Pushes the specified context onto the stack.
        /// </summary>
        /// <param name="context">The context.</param>
        public static void Push(GameContext context)
        {
            _stackLock.WaitOne();
            _stack.Push(context);
        }

        /// <summary>
        /// Pushes the specified context onto the stack for the current thread only.
        /// </summary>
        /// <param name="context">The context.</param>
        public static void PushThreadContext(GameContext context)
        {
            ThreadStack.Push(context);
        }

        /// <summary>
        /// Pops a context off the top of the thread-specific stack.
        /// </summary>
        /// <returns>The popped context, or <c>null</c> if the stack is empty.</returns>
        public static GameContext PopThreadContext()
        {
            GameContext result;

            if (!ThreadStack.TryPop(out result))
                return result;

            return null;
        }

        /// <summary>
        /// Checks to see if the <see cref="GameContext"/> currently at the top of the
        /// stack matches <paramref name="context"/> and performs a Pop()
        /// only if it was pushed by the current thread.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <returns><c>true</c> if successful; otherwise, <c>false</c>.</returns>
        public static bool CheckAndPop(GameContext context)
        {
            if (!_stack.CompareAndPop(context))
                return false;
            _stackLock.ReleaseMutex();
            return true;
        }

        /// <summary>
        /// Pops a context off the top of the stack.
        /// </summary>
        /// <returns>The popped context, or <c>null</c> if the stack is empty.</returns>
        public static GameContext Pop()
        {
            GameContext result;
            if (!_stack.TryPop(out result))
                return null;
            _stackLock.ReleaseMutex();
            return result;
        }

        /// <summary>
        /// Gets the context at the top of the stack.
        /// </summary>
        /// <returns>The context.</returns>
        public static GameContext Peek()
        {
            GameContext result;
            if (!_stack.TryPeek(out result))
                return null;
            return result;
        }

        /// <summary>
        /// Gets the context at the top of the stack.
        /// </summary>
        /// <value>The context.</value>
        public static GameContext Current
        {
            get { return ThreadContext ?? Peek(); }
        }

        public static GameContext ThreadContext
        {
            get
            {
                GameContext context;

                if (ThreadStack.TryPeek(out context))
                    return context;

                return null;
            }
        }

        /// <summary>
        /// Creates a new instance using the specified options.
        /// </summary>
        /// <param name="options">The options.</param>
        /// <param name="isMultiplayerGame">Specifies if the game is multiplayer.</param>
        /// <returns>The new instance.</returns>
        public static GameContext Create(GameOptions options, bool isMultiplayerGame)
        {
            try
            {
                var game = new GameContext(options)
                           {
                               IsMultiplayerGame = isMultiplayerGame
                           };
                return game;
            }
            catch (Exception e)
            {
                throw new SupremacyException(
                    "An error occurred while creating a new game context.",
                    e,
                    SupremacyExceptionAction.Disconnect);
            }
        }
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="GameContext"/> class
        /// using the specified options.
        /// </summary>
        /// <param name="options">The options.</param>
        private GameContext(GameOptions options)
        {
            _options = options;
            Initialize();
        }

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        private void Initialize()
        {
            Push(this);
            try
            {
                _gameMod = GameModLoader.GetModFromCommandLine();
                _races = RaceDatabase.Load();
                _civilizations = CivDatabase.Load();
                _civManagers = new CivilizationManagerMap();
                _tables = GameTables.Load();
                _techDatabase = TechDatabase.Load();
                _researchMatrix = ResearchMatrix.Load();
                _techTrees = new TechTreeMap();
                _strategyDatabase = StrategyDatabase.Load();
                _diplomacyStatus = new CivilizationPairedMap<DiplomacyStatus>();
                _scriptedEvents = new List<ScriptedEvent> { new ReligiousHolidayEvent() };
                _agentDatabase = AgentDatabase.Load();

                GalaxyGenerator.GenerateGalaxy(this);

                TechTree.LoadTechTrees(this);

                foreach (var civManager in _civManagers)
                {
                    foreach (var colony in civManager.Colonies)
                    {
                        ColonyBuilder.Build(colony);
                        civManager.TotalPopulation.AdjustCurrent(colony.Population.CurrentValue);
                    }
                }

                _sectorClaims = new SectorClaimGrid();
                _diplomats = new CivilizationKeyedMap<Diplomat>(o => o.OwnerID);
            }
            finally
            {
                Pop();
            }
        }

        /// <summary>
        /// Update references lost during reserialization.
        /// </summary>
        internal void OnDeserialized()
        {
            var needsPush = !ReferenceEquals(Current, this);
            if (needsPush)
                Push(this);

            try
            {
                _universe.OnDeserialized();
            }
            finally
            {
                if (needsPush)
                    Pop();
            }
        }
    }
}
